#include "zen_app_windows.h"
#include <thread>

namespace Zen
{
	std::shared_ptr<Zen::App> App::Only()
	{
		return AppWindows_::Only_();
	}

	std::shared_ptr<AppWindows_> AppWindows_::Only_()
	{
		static std::shared_ptr<AppWindows_> me = std::make_shared<AppWindows_>();
		return me;
	}

	LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
	{
		if (AppWindows_::Only_()->GetHWindow() == hWnd)
		{
			return AppWindows_::Only_()->OnMessage_(message, wParam, lParam);
		}
		return DefWindowProcA(hWnd, message, wParam, lParam);
	}

	void AppWindows_::Initialize(HINSTANCE instance)
	{
		mHInstance = instance;

		auto del = ZenAppMain(&mConfig);

		SetFramesPerSecond(mConfig.FramesPerSecond);

		__zen_must_else(del, "invalid delegate from ZenAppMain");

		mDelegate = del;

		static const auto ClassName = "ZenAppWindow";

		tagWNDCLASSEXA wcex{};
		wcex.cbSize = sizeof(WNDCLASSEX);
		wcex.style = CS_HREDRAW | CS_VREDRAW;
		wcex.lpfnWndProc = WndProc;
		wcex.cbClsExtra = 0;
		wcex.cbWndExtra = 0;
		wcex.hInstance = mHInstance;
		wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
		wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
		wcex.lpszClassName = ClassName;
		__zen_must_else(RegisterClassExA(&wcex), "failed to register class");

		//style |= WS_CAPTION | WS_BORDER;
		RECT rect{ 0, 0, mConfig.WindowWidth, mConfig.WindowHeight };
		AdjustWindowRect(&rect, (DWORD)mConfig.Style, FALSE);
		auto width = rect.right - rect.left;
		auto height = rect.bottom - rect.top;

		mHWND = CreateWindowA(ClassName, mConfig.Title.c_str(), (DWORD)mConfig.Style, CW_USEDEFAULT, 0, width, height, nullptr, nullptr, mHInstance, nullptr);

		__zen_must_else(mHWND, "failed to create window");

		mWinSize = { float(width), float(height) };

		ShowWindow(mHWND, 1);
		UpdateWindow(mHWND);
	}

	union LongParam
	{
		int64_t i64{};
		int32_t i32[2];
		int16_t i16[4];
	};
	LRESULT AppWindows_::OnMessage_(UINT message, WPARAM wParam, LPARAM lParam)
	{
		LongParam params{};
		params.i64 = lParam;

		if (!mDelegate && false)
		{
			switch (message)
			{
			case WM_DESTROY: PostQuitMessage(0); break;
			default: return DefWindowProcA(mHWND, message, wParam, lParam); break;
			}
		}
		else
		{
			switch (message)
			{
			case WM_LBUTTONDOWN: {
				OnMouseKeyDown(MouseButton::Left, params.i16[0], params.i16[1]);
				break;
			}
			case WM_LBUTTONUP: {
				OnMouseKeyUp(MouseButton::Left, params.i16[0], params.i16[1]);
				break;
			}
			case WM_RBUTTONDOWN: {
				OnMouseKeyDown(MouseButton::Right, params.i16[0], params.i16[1]);
				break;
			}
			case WM_RBUTTONUP: {
				OnMouseKeyUp(MouseButton::Right, params.i16[0], params.i16[1]);
				break;
			}
			case WM_MBUTTONDOWN: {
				OnMouseKeyDown(MouseButton::Middle, params.i16[0], params.i16[1]);
				break;
			}
			case WM_MBUTTONUP: {
				OnMouseKeyUp(MouseButton::Middle, params.i16[0], params.i16[1]);
				break;
			}
			case WM_MOUSEMOVE: {
				AppMouse mouse{ MouseButton::None, (float)params.i16[0], (float)params.i16[1] };
				mDelegate->OnMouseMove(mouse);
				break;
			}
			case WM_DESTROY: {
				mDelegate->OnExit();
				PostQuitMessage(0);
				break;
			}
			case WM_TIMER: {
				break;
			}
			case WM_SIZE: {
				mWinSize = { (float)params.i16[0], (float)params.i16[1] };
				if (!mIsLaunched)
				{
					mDelegate->OnLaunch(mWinSize);
					mIsLaunched = true;
				}
				else
				{
					mDelegate->OnResize(mWinSize);
				}
				break;
			}
			case WM_PAINT: {
				if (mIsLaunched)
				{
					PAINTSTRUCT ps{};
					HDC hdc = BeginPaint(mHWND, &ps);
					mDelegate->OnDraw();
					EndPaint(mHWND, &ps);
				}
				mPainted = true;
				break;
			}
			default: {
				return DefWindowProcA(mHWND, message, wParam, lParam);
			}
			}
		}
		return 0;
	}

	void AppWindows_::OnMouseKeyDown(MouseButton key, int x, int y)
	{
		if (mMouseKeyDown & int(key))
		{
			OnMouseKeyUp(key, x, y);
		}

		printf("down \n");
		SetCapture(mHWND);

		mMouseKeyDown |= int(key);
		AppMouse mouse{ key, (float)x, (float)y };
		mDelegate->OnMouseKeyDown(mouse);
	}

	void AppWindows_::OnMouseKeyUp(MouseButton key, int x, int y)
	{
		printf("up \n");
		if (mMouseKeyDown & int(key))
		{
			AppMouse mouse{ key, (float)x, (float)y };
			mDelegate->OnMouseKeyUp(mouse);

			mMouseKeyDown &= ~int(key);
			if (mMouseKeyDown == 0)
			{
				ReleaseCapture();
			}
		}
	}

	HINSTANCE AppWindows_::GetHInstance()
	{
		return mHInstance;
	}

	HWND AppWindows_::GetHWindow()
	{
		return mHWND;
	}

	std::tuple<int, int> AppWindows_::GetScreenSize()
	{
		RECT rect{};
		GetWindowRect(mHWND, &rect);
		return std::tuple<int, int>(rect.right - rect.left, rect.bottom - rect.top);
	}

	auto AppWindows_::GetRuntimeDelegate() -> std::shared_ptr<AppDelegate>
	{
		return std::shared_ptr<AppDelegate>();
	}

	bool AppWindows_::IsMultiTouchEnabled()
	{
		return false;
	}

	void AppWindows_::SetMultiTouchEnabled(bool v)
	{
		// not supported on windows.
	}

	bool AppWindows_::IsRotatable()
	{
		return false;
	}

	void AppWindows_::SetRotatable(bool)
	{
		// not supported on windows.
	}

	float AppWindows_::GetFramesPerSecond()
	{
		return mConfig.FramesPerSecond;
	}

	void AppWindows_::SetFramesPerSecond(float fps)
	{
		mConfig.FramesPerSecond = fps;
		mUpdateInterval = std::chrono::microseconds(int64_t(95e4 / fps));
	}

	bool AppWindows_::IsStatusVisible()
	{
		return false;
	}

	void AppWindows_::SetStatusVisible(bool)
	{
		// not supported on windows.
	}

	void AppWindows_::SetLowFrameRate(int) {}

	int AppWindows_::GetLowFrameRate()
	{
		return 0;
	}

	bool AppWindows_::IsBGUpdatable()
	{
		return mConfig.IsBGUpdatable;
	}

	void AppWindows_::SetBGUpdatable(bool)
	{
		mConfig.IsBGUpdatable;
	}

	void AppWindows_::Terminate() {}

	void AppWindows_::MainLoop()
	{
		MSG msg;
		while (1)
		{
			if (PeekMessageA(&msg, mHWND, 0, 0, PM_REMOVE))
			{
				if (msg.message == WM_QUIT) break;
				TranslateMessage(&msg);
				DispatchMessageA(&msg);
			}
			else if (mIsLaunched && mPainted)
			{
				auto wait = mUpdateInterval + mLastUpdate - std::chrono::steady_clock::now();
				if (wait > std::chrono::milliseconds(10))
				{
					std::this_thread::sleep_for(std::chrono::milliseconds(5));
					continue;
				}
				if (wait.count() > 0)
				{
					std::this_thread::sleep_for(wait);
				}
				mLastUpdate = std::chrono::steady_clock::now();
				mDelegate->OnUpdate();
				PostMessageA(mHWND, WM_PAINT, 0, 0);
				mPainted = false;
			}
		}
	}
} // namespace Zen

int APIENTRY __wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
	Zen::AppWindows_::Only_()->Initialize(hInstance);
	Zen::AppWindows_::Only_()->MainLoop();

	return 0;
}

int main()
{
	__wWinMain(GetModuleHandle(nullptr), nullptr, nullptr, 1);
}
